import { debounce } from 'lodash-es';
import React, { useCallback, useReducer, useState } from 'react';
import CacheContext from './CacheContext';
import * as cacheTypes from './cache-types';
import cacheReducer from './cacheReducer';

function KeepAliveProvider(props: any) {
  const [cacheStates, dispatch] = useReducer(cacheReducer, {});
  const [activeCacheObj, setActiveCacheObj] = useState<Record<string, any[]>>(
    {},
  );
  const mount = useCallback(
    ({ cacheId, element }: { cacheId: string; element: React.ReactNode }) => {
      // 挂载元素方法，提供子组件调用挂载元素
      if (cacheStates[cacheId]) {
        const cacheState = cacheStates[cacheId];
        if (cacheState.status === cacheTypes.DESTROY) {
          const doms = cacheState.doms;
          doms?.forEach((dom) => dom?.parentNode?.removeChild(dom));
          dispatch({ type: cacheTypes.CREATE, payload: { cacheId, element } }); // 创建缓存
        }
      } else {
        console.log(
          '--2.KeepAliveProvider--mount--组件初次加载创建element--:',
          cacheId,
        );
        dispatch({ type: cacheTypes.CREATE, payload: { cacheId, element } }); // 创建缓存
      }
    },
    [cacheStates],
  );
  const handleScroll = debounce(
    // 缓存滚动条
    (cacheId: string, event: React.WheelEvent) => {
      if (cacheStates[cacheId]) {
        // console.log('--KeepAliveProvider--scroll----');
        const { target } = event;
        const scrolls = cacheStates[cacheId].scrolls;
        scrolls![target as any] = (target as any).scrollTop;
      }
    },
    500,
  );

  // appendChild(dom)时[缓存被激活]，执行 cacheWatch注册的函数
  const activeCache = useCallback(
    (cacheId: string) => {
      if (activeCacheObj[cacheId]) {
        activeCacheObj[cacheId].forEach((fn: any) => {
          fn(cacheId);
        });
      }
    },
    [cacheStates, activeCacheObj],
  );

  //注册激活监听函数
  const cacheWatch = useCallback(
    (ids: string[], fn: any) => {
      ids.forEach((id: string) => {
        if (activeCacheObj[id]) {
          activeCacheObj[id].push(fn);
        } else {
          activeCacheObj[id] = [fn];
        }
      });
      setActiveCacheObj({
        ...activeCacheObj,
      });
    },
    [activeCacheObj],
  );

  //移除激活监听函数
  const removeCacheWatch = useCallback(
    (ids: string[], fn: any) => {
      ids.forEach((id: string) => {
        if (activeCacheObj[id] && activeCacheObj[id].length > 0) {
          const index = activeCacheObj[id].indexOf(fn);
          activeCacheObj[id].splice(index, 1);
        }
      });
      setActiveCacheObj({
        ...activeCacheObj,
      });
    },
    [activeCacheObj],
  );

  return (
    <CacheContext.Provider
      value={{
        mount,
        cacheStates,
        dispatch,
        handleScroll,
        activeCache,
        cacheWatch,
        removeCacheWatch,
      }}
    >
      {props.children}
      {Object.values(cacheStates)
        .filter((cacheState) => cacheState.status !== cacheTypes.DESTROY)
        .map(({ cacheId, element }) => (
          <div
            style={{ display: 'none' }}
            id={`cache_${cacheId}`}
            key={cacheId}
            // 原生div中声明ref，当div渲染到页面，会执行ref中的回调函数，这里在id为cache_${cacheId}的div渲染完成后，会继续渲染子元素,滚动监听挂载doms上，这里doms要保证length === 1,
            ref={(dom) => {
              const cacheState = cacheStates[cacheId];
              if (
                dom &&
                (!cacheState.doms || cacheState.status === cacheTypes.DESTROY)
              ) {
                console.log(
                  '--3.KeepAliveProvider--把withKeepAlive包裹的组件的虚拟dom放在div下渲染就是为了获取虚拟dom对应的真实dom---',
                );
                const doms = Array.from(dom.childNodes);
                dispatch({
                  type: cacheTypes.CREATED,
                  payload: { cacheId, doms },
                });
              }
            }}
          >
            {element}
          </div>
        ))}
    </CacheContext.Provider>
  );
}

const useCacheContext = () => {
  const context = React.useContext(CacheContext);
  if (!context) {
    throw new Error('useCacheContext必须在Provider中使用');
  }
  return context;
};

export { KeepAliveProvider, useCacheContext };
